using System;
using System.Collections.Generic;
using System.Text;
using Jbe.TestSuite.TestDevice.FunctionGen.Interface.Services;
using Jbe.TestSuite.TestDevice.Manager.Interface.Services;
using Jbe.TestSuite.TestDevice.FunctionGen.Interface.Constants;
using System.Threading;
using Jbe.TestSuite.TestDevice.Manager.Interface;
using Microsoft.Practices.CompositeUI.EventBroker;
using System.Diagnostics;

namespace Jbe.TestSuite.TestDevice.FunctionGen.Driver.Services
{
    public class FunctionGenDriver : IFunctionGen, IDisposable
    {
        [EventPublication(EventTopicNames.Sample, PublicationScope.Global)]
        public event EventHandler<SampleEventArgs> Sample;

        /// <summary>
        /// Define a delegate for calculation of the wave form amplitude at the specified time.
        /// </summary>
        public delegate double WaveFormStrategy(double amplitude, double frequency, double time);

        private IFunctionGenConfig config;
        private Timer timer;
        private bool isActive;
        private DateTime startTime;

        public IFunctionGenConfig Config
        {
            get { return config; }
            set 
            {
                if (!config.Equals(value))
                {
                    value.Lock();
                    Interlocked.Exchange<IFunctionGenConfig>(ref config, value);
                }
            }
        }

        public bool IsActive
        {
            get { return isActive; }
        }

        public FunctionGenDriver()
        {
            config = new FunctionGenConfig();
            config.WaveForm = WaveForm.Sine;
            config.Amplitude = 50;
            config.Frequency = 2;

            timer = new Timer(new TimerCallback(FireSignalSample));
            isActive = false;
        }

        public void Start()
        {
            isActive = true;
            startTime = DateTime.Now;
            timer.Change(0, 15);
        }

        public void Stop()
        {
            timer.Change(0, Timeout.Infinite);
            isActive = false;
        }

        public void Dispose()
        {
            timer.Dispose();
        }

        protected virtual WaveFormStrategy GetStrategy(WaveForm waveForm)
        {
            switch (Config.WaveForm)
            {
                case WaveForm.Sine:
                    return SineStrategy;
                case WaveForm.Square:
                    return SquareStrategy;
                case WaveForm.Triangle:
                    return TriangleStrategy;
                case WaveForm.Sawtooth:
                    return SawtoothStrategy;
                default:
                    throw new NotSupportedException("The wave form is not supported by this driver.");
            }
        }

        protected virtual void OnSample(double amplitude, DateTime timeStamp)
        {
            if (Sample != null) { Sample(this, new SampleEventArgs(amplitude, timeStamp)); }
        }

        private void FireSignalSample(object state)
        {
            DateTime now = DateTime.Now;
            TimeSpan time = now.Subtract(startTime);
            
            double amplitude = GetStrategy(Config.WaveForm)(Config.Amplitude, Config.Frequency, time.TotalSeconds);
            OnSample(amplitude, now);
        }

        #region WaveForm Strategies

        private double SineStrategy(double amplitude, double frequency, double time)
        {
            return amplitude * Math.Sin(2 * Math.PI * frequency * time);
        }

        private double SquareStrategy(double amplitude, double frequency, double time)
        {
            double normalizedTime = time - Math.Floor(time * frequency) / frequency;
            normalizedTime = (2 * normalizedTime * frequency) - 1;

            return (normalizedTime >= 0) ? -amplitude : amplitude;
        }

        private double TriangleStrategy(double amplitude, double frequency, double time)
        {
            time -= 0.25 / frequency;
            double normalizedTime = time - Math.Floor(time * frequency) / frequency;
            normalizedTime = (2 * normalizedTime * frequency) - 1;

            return 2 * amplitude * Math.Sign(normalizedTime) * normalizedTime - amplitude;
        }

        private double SawtoothStrategy(double amplitude, double frequency, double time)
        {
            time -= 0.5 / frequency;
            double normalizedTime = time - Math.Floor(time * frequency) / frequency;
            normalizedTime = normalizedTime * frequency;

            return 2 * amplitude * normalizedTime - amplitude;
        }

        #endregion
    }
}
